#include "Texture.h"

using namespace OBR;

template <typename DataType, int dim> Texture<DataType, dim>::Texture()
{
}

template <typename DataType, int dim> Texture<DataType, dim>::Texture(DataType default_value)
{
    uint sizes[dim];
    for (size_t i = 0; i < dim; i++)
    {
        sizes[i] = 1;
    }
    this->load(sizes, &default_value);
}

template <typename DataType, int dim>
Texture<DataType, dim>::Texture(const Texture<DataType, dim> &src)
    : initialized(src.initialized), d_texture_data(src.d_texture_data)
{
    uint datacnt = 1;
    for (size_t d = 0; d < dim; d++)
    {
        datacnt *= src.size[d];
        this->size[d] = src.size[d];
    }
    this->data = new DataType[datacnt];
    memcpy(this->data, src.data, datacnt * sizeof(DataType));
}

template <typename DataType, int dim>
Texture<DataType, dim> &Texture<DataType, dim>::operator=(const Texture<DataType, dim> &src)
{
    // initialize state
    this->initialized = src.initialized;
    // calc & move datasize
    uint datacnt = 1;
    for (size_t d = 0; d < dim; d++)
    {
        datacnt *= src.size[d];
        this->size[d] = src.size[d];
    }
    // move data
    this->data = new DataType[datacnt];
    memcpy(this->data, src.data, datacnt * sizeof(DataType));
    // move device data
    this->d_texture_data.resize(datacnt * sizeof(DataType));
    this->d_texture_data.upload(this->data, datacnt * sizeof(DataType));

    return *this;
}

template <typename DataType, int dim> Texture<DataType, dim>::~Texture()
{
    if (this->data)
    {
        delete[] this->data;
    }
}

template <typename DataType, int dim> void Texture<DataType, dim>::upload()
{
    uint datacnt = 1;
    for (size_t i = 0; i < dim; i++)
    {
        datacnt *= this->size[i];
    }
    // upload to cuda device
    this->d_texture_data.resize(sizeof(DataType) * datacnt);
    this->d_texture_data.upload(this->data, sizeof(DataType) * datacnt);
}

template <typename DataType, int dim>
void Texture<DataType, dim>::load(uint *_size, DataType *_data)
{
    // compute sizes
    uint datacnt = 1;
    for (size_t i = 0; i < dim; i++)
    {
        datacnt *= _size[i];
    }
    // save sizes and data
    this->data = new DataType[datacnt];
    memcpy(this->data, _data, sizeof(DataType) * datacnt);
    memcpy(this->size, _size, sizeof(uint) * dim);
    // upload to cuda device
    this->d_texture_data.resize(sizeof(DataType) * datacnt);
    this->d_texture_data.upload(_data, sizeof(DataType) * datacnt);
}

template <typename DataType, int dim>
DeviceTexture2D<DataType> Texture<DataType, dim>::getDeviceTex2D()
{
    assert(dim == 2);
    DeviceTexture2D<DataType> d_tex;
    d_tex.size = make_int2(this->size[0], this->size[1]);
    d_tex.data = reinterpret_cast<DataType *>(this->d_texture_data.d_pointer());
    return d_tex;
}

template <typename DataType, int dim>
DeviceTexture3D<DataType> Texture<DataType, dim>::getDeviceTex3D()
{
    assert(dim == 3);
    DeviceTexture3D<DataType> d_tex;
    d_tex.size = make_int3(this->size[0], this->size[1], this->size[2]);
    d_tex.data = reinterpret_cast<DataType *>(this->d_texture_data.d_pointer());
    return d_tex;
}

template <typename DataType, int dim> DataType &Texture<DataType, dim>::at(uint x, uint y, uint z)
{
    if constexpr (dim == 1)
    {
        return this->data[x];
    }
    else if constexpr (dim == 2)
    {
        return this->data[x * this->size[1] + y];
    }
    else if constexpr (dim == 3)
    {
        return this->data[x * this->size[1] * this->size[2] + y * this->size[2] + z];
    }
    else
    {
        std::cerr << "calling at() from texture dim > 3 is not supported" << std::endl;
        throw std::runtime_error("calling at() from texture dim > 3 is not supported");
    }
    return this->data[0];
}

/**
 * @brief see for reasons
 * https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
 *
 */
/* --------------------- instances of possible textures --------------------- */
template class Texture<Spectrum, 2>;
template class Texture<Spectrum, 3>;
template class Texture<float, 2>;
template class Texture<float, 3>;
/* -------------------------------------------------------------------------- */